package de.slothsoft.commons.parser;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class CsvParser<T> implements Parser<T> {

	private static final String LINE_BREAK = System
			.getProperty("line.separator");

	private final Class<T> resultClass;

	private String separator = ",";

	public CsvParser(Class<T> resultClass) {
		super();
		this.resultClass = resultClass;
	}

	@Override
	public String format(List<T> input) throws ParserException {
		Field[] allFields = getDeclaredFields();
		StringBuilder sb = new StringBuilder();
		formatHeader(sb, allFields);
		for (T element : input) {
			for (int i = 0; i < allFields.length; i++) {
				if (i > 0) {
					sb.append(this.separator);
				}
				try {
					sb.append(allFields[i].get(element));
				} catch (Exception e) {
					throw new ParserException(e);
				}
			}
			sb.append(LINE_BREAK);
		}
		return sb.toString();
	}

	private void formatHeader(StringBuilder sb, Field[] allFields) {
		for (int i = 0; i < allFields.length; i++) {
			if (i > 0) {
				sb.append(this.separator);
			}
			sb.append(allFields[i].getName());
		}
		sb.append(LINE_BREAK);
	}

	private Field[] getDeclaredFields() {
		Field[] result = this.resultClass.getDeclaredFields();
		for (Field field : result) {
			field.setAccessible(true);
		}
		return result;
	}

	@Override
	public List<T> parse(String input) throws ParserException {
		String[] lines = input.split("(\n\r|\r\n|\n|\r)");
		if (lines.length < 2)
			return new ArrayList<>();
		Field[] usedFields = findUsedFields(this.resultClass,
				lines[0].split(this.separator));
		return doParse(lines, this.resultClass, usedFields);
	}

	private List<T> doParse(String[] lines, Class<T> resultClass,
			Field[] usedFields) throws ParserException {
		List<T> result = new ArrayList<>();
		for (int i = 1; i < lines.length; i++) {
			String line = lines[i];
			if (!line.isEmpty()) {
				result.add(parseLine(line, resultClass, usedFields));
			}
		}
		return result;
	}

	private <T> T parseLine(String line, Class<T> resultClass,
			Field[] usedFields) throws ParserException {
		try {
			T result = resultClass.newInstance();
			String[] cells = line.split(this.separator);
			int minLength = Math.min(cells.length, usedFields.length);
			for (int i = 0; i < minLength; i++) {
				if (usedFields[i] != null) {
					usedFields[i].set(result, cells[i]);
				}
			}
			return result;
		} catch (Exception e) {
			throw new ParserException(e);
		}
	}

	public Field[] findUsedFields(Class<?> resultClass, String[] header) {
		Field[] result = new Field[header.length];
		for (int i = 0; i < result.length; i++) {
			result[i] = findField(resultClass, header[i]);
		}
		return result;
	}

	private Field findField(Class<?> resultClass, String string) {
		try {
			Field field = resultClass.getDeclaredField(string);
			field.setAccessible(true);
			return field;
		} catch (Exception e) {
			return null;
		}
	}

	public String getSeparator() {
		return this.separator;
	}

	public void setSeparator(String separator) {
		this.separator = separator;
	}

	public CsvParser<T> separator(String separator) {
		this.separator = separator;
		return this;
	}

}
